﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;

namespace Citrix.XaAdminMobile.Util
{
    /// <summary>
    /// Takes multiple scroll bar values to determine the number of pages that can be scrolled
    /// and returns a boolean that states whether scroll overlay buttons should be shown. When
    /// the scroll bar doesn't have far to scroll we return false. When there are two or more
    /// pages to scroll we return true.
    /// 
    /// Doing this reduces clutter on the UI by not showing extra scroll buttons on the UI when
    /// there isn't enough pages to scroll to make them worthwhile.
    /// </summary>
    internal class HideLowPagesScrollButtonConverter : IMultiValueConverter
    {
        /// <summary>
        /// This is the minimum number of pages worth of scrolling we require before we show the
        /// scroll buttons registered with the attached properties in this class.
        /// </summary>
        private const double NumPagesToShowButtons = 2;

        /// <summary>
        /// Converts scroll bar extent/viewport state into a boolean of whether to show scroll
        /// overlay buttons.
        /// </summary>
        /// <param name="values">Array of:
        /// 0 = max scroll bar range;
        /// 1 = the viewport size;
        /// 2 = visibility of the associated scroll bar;
        /// 3 = the scroll bar itself.</param>
        /// <param name="targetType"></param>
        /// <param name="parameter"></param>
        /// <param name="culture"></param>
        /// <returns></returns>
        /// <remarks>
        /// The binding needs to include state that is modified when either the viewport or
        /// scroll extent changes in order to trigger re-evaluation. The first two values in our
        /// value array give us that. We also supply the scroll bar so we can use it to find
        /// an accurate value for the scroll extent (via a lookup on the parent scroll viewer)
        /// as I haven't found any convenient value we can bind off the scroll bar to get it
        /// directly.
        /// 
        /// Also I wasn't able to find a way to bind to the ScrollViewer directly in the style
        /// triggers which is why this solution is designed around binding to ScrollBars instead.
        /// </remarks>
        public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            if (values.Length == 4 && values[0] is double &&
                values[1] is double && values[2] is Visibility &&
                values[3] is ScrollBar)
            {
                Visibility vis = (Visibility)values[2];
                ScrollBar scrollBar = (ScrollBar)values[3];

                // Only apply logic when the scrollbar is visible. We don't want to force a
                // button to the Hidden state (from collapsed) and make it take up space in the
                // layout. If we didn't do this various elements that contain scroll viewers but
                // often don't use them, would be too big, e.g. text boxes.
                if (vis == Visibility.Visible)
                {
                    double extent = GetScrollExtent(scrollBar);

                    double viewport = (double)values[1];
                    viewport = Math.Max(1, viewport);
                    extent = Math.Max(extent, viewport);

                    double pages = extent / viewport;

                    // If we have a small number of pages then we return true to hide them.
                    if (pages < NumPagesToShowButtons)
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        /// <summary>
        /// Not supported.
        /// </summary>
        /// <param name="value"></param>
        /// <param name="targetTypes"></param>
        /// <param name="parameter"></param>
        /// <param name="culture"></param>
        /// <returns></returns>
        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotSupportedException();
        }

        /// <summary>
        /// The scrollbar doesn't seem to have any easy way to get an accurate value for the full
        /// scroll extent, so instead we lookup the scroll viewer and query it from that instead.
        /// </summary>
        /// <param name="scrollBar">The scrollbar.</param>
        /// <returns>The scroll extent.</returns>
        private double GetScrollExtent(ScrollBar scrollBar)
        {
            var scrollViewer = FindVisualParent<System.Windows.Controls.ScrollViewer>(scrollBar);
            if (null != scrollViewer)
            {
                if (scrollBar.Orientation == Orientation.Vertical)
                {
                    return scrollViewer.ExtentHeight;
                }
                else
                {
                    return scrollViewer.ExtentWidth;
                }
            }
            return 0;
        }

        /// <summary>
        /// Finds the parent of the specified type.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="obj"></param>
        /// <returns>The parent or null if not found.</returns>
        private static T FindVisualParent<T>(DependencyObject obj)
            where T : DependencyObject
        {
            var parent = System.Windows.Media.VisualTreeHelper.GetParent(obj);
            if (parent != null && parent is T)
            {
                return (T)parent;
            }
            else
            {
                T parentOfParent = FindVisualParent<T>(parent);
                if (parentOfParent != null)
                {
                    return parentOfParent;
                }
            }
            return null;
        }
    }
}
